using System.Collections.Generic;
using RollManager.Library.Utils;
using System.Reflection;
using System;
using System.ComponentModel;

namespace RollManager.Library.ActionSteps
{
    [Serializable]
    public abstract class ActionStep
    {
        [NonSerialized]
        private Dictionary<string, string> m_originalStrings;

        [NonSerialized]
        private ProgressReporter m_progress;

        [NonSerialized]
        private int m_serverIdx;

        [NonSerialized]
        private List<string> m_servers;

        [NonSerialized]
        private Dictionary<string, string> m_context;

        [NonSerialized]
        protected object Output = null;

        private StepFrequency m_timing = StepFrequency.Always;
        private string m_alternateServer;

        [Description( "This sets what the server should be as the source for this action.  This is optional and allows the server on which this is performed to be changed from the default server in the server group." )]
        public string AlternateServer
        {
            get
            {
                return m_alternateServer;
            }
            set
            {
                m_alternateServer = value.Trim();
            }
        }

        [Description( "Whether to have the action take place on the first server, last server, or all servers in the server group." )]
        public StepFrequency Frequency
        {
            get
            {
                return m_timing;
            }
            set
            {
                m_timing = value;
            }
        }

        [Browsable(false)]
        public abstract string Name
        {
            get;
        }

        [Browsable( false )]
        protected ProgressReporter Progress
        {
            get
            {
                return m_progress;
            }
        }

        [Browsable( false )]
        protected Dictionary<string,string> Context
        {
            get { return m_context; }
        }

        [Browsable( false )]
        protected string CurrentServer
        {
            get
            {
                return string.IsNullOrEmpty( m_alternateServer ) ? m_servers[m_serverIdx] : m_alternateServer;
            }
        }

        [Browsable( false )]
        protected string FirstServer
        {
            get
            {
                return m_servers[ 0 ];
            }
        }

        [Browsable( false )]
        protected bool LastServer
        {
            get
            {
                return m_serverIdx >= m_servers.Count - 1;
            }
        }

        public Object RunStep( ProgressReporter progress, int serverIdx, List<string> servers, bool simulation, Dictionary<string, string> context )
        {
            m_progress = progress;
            m_serverIdx = serverIdx;
            m_servers = servers;
            m_context = context;
            m_originalStrings = new Dictionary<string, string>();

            RunStep( simulation );

            return Output;
        }

        private void RunStep( bool simulation )
        {
            try
            {
                // setup config strings
                setupStrings();

                // determine whether the step should be run.
                bool runStep = false;
                switch( m_timing )
                {
                    case StepFrequency.First:
                        runStep = m_serverIdx == 0;
                        break;

                    case StepFrequency.Last:
                        runStep = LastServer;
                        break;

                    case StepFrequency.Always:
                        runStep = true;
                        break;

                    default:
                        throw new ArgumentOutOfRangeException();
                }

                if( runStep )
                {
                    m_progress.AddFormat( "--- Starting {0} on {1} ---", Name, CurrentServer );

                    if( simulation )
                    {
                        Simulate();
                    }
                    else
                    {
                        Run();
                    }

                    m_progress.AddFormat( "--- Finished {0} on {1} ---", Name, CurrentServer );
                    m_progress.ReportProgress();
                }
            }
            finally
            {
                restoreStrings();
            }
        }

        private void restoreStrings()
        {
            foreach( KeyValuePair<string,string> entry in m_originalStrings)
            {
                PropertyInfo prop = GetType().GetProperty( entry.Key );
                prop.SetValue( this, entry.Value, null );
            }

            m_originalStrings.Clear();
        }

        protected abstract void Run();
        protected abstract void Simulate();

        private void setupStrings()
        {
            // Substitute in the config values in the strings of this step.
            foreach( PropertyInfo prop in GetType().GetProperties())
            {
                if( prop.PropertyType == typeof( string ) && prop.GetSetMethod() != null )
                {
                    string oldValue = (string) prop.GetValue( this, null );
                    if( string.IsNullOrEmpty( oldValue ) )
                    {
                        continue;
                    }

                    string newValue = setupString( oldValue );
                    prop.SetValue( this, newValue, null );

                    // save the old value
                    m_originalStrings.Add( prop.Name, oldValue );
                }
            }
        }

        public string setupString( string str )
        {
            if( string.IsNullOrEmpty( str ) )
            {
                return str;
            }

            StringParser text = new StringParser( str );
            string nextPiece = null;

            while( ( nextPiece = text.NextPiece ) != null )
            {
                switch( nextPiece )
                {
                    case "USERNAME":
                        text += Common.UserName;
                        break;

                    case "SERVER":
                        text += CurrentServer;
                        break;

                    default:
                        string replacement;
                        if( m_context.TryGetValue( nextPiece, out replacement ) )
                        {
                            text += replacement;
                        }
                        else
                        {
                            text += Database.GetUserSetting( nextPiece, Common.UserId );
                        }
                        break;
                }
            }

            return text.ToString();
        }
    }
}